#pragma once

//基于阻塞队列的生产者消费者模型
#include <iostream>
#include <queue>
#include <pthread.h>

const int gmaxcap = 5;

template <class T>
class BlockQueue
{

public:
    BlockQueue(const int &maxcap = gmaxcap):_maxcap(maxcap)
    {
        pthread_mutex_init(&_mutex,nullptr);
        pthread_cond_init(&_pcond,nullptr);
        pthread_cond_init(&_ccond,nullptr);
    }
    void push(const T &in)//输入型参数 const &
    {
        pthread_mutex_lock(&_mutex);//加锁
        //1.判断
        while(is_full())
        {
            pthread_cond_wait(&_pcond,&_mutex); //对列满的话无法生产，要进行等待  解锁并等待  当线程被唤醒时，会自动重新获取这个互斥锁
        }
        //2.走到这里一定没有满
        _q.push(in);
        //3.这时queue中一定有元素，可以唤醒c进行消耗，这里可以有一定的策略
        // pthread_cond_signal(&_ccond);// 只唤醒，被唤醒时会自动重新获取这个互斥锁
        pthread_mutex_unlock(&_mutex);//解锁
        pthread_cond_signal(&_ccond);// 只唤醒，被唤醒时会自动重新获取这个互斥锁
        //这里也可以将唤醒的操作移到临界区的外面（并且这里还优化了效率）
        //a.先解锁再 signal 可以减少锁的持有时间
        //b.被唤醒的线程可以立即获取锁（而不是等待当前线程释放锁后才被唤醒
    }

    void pop(T *out)//输出型参数 *  ， // 输出输出型参数 &
    {
        pthread_mutex_lock(&_mutex);//加锁
        //1.判断
        while(is_empty())
        {
            pthread_cond_wait(&_ccond,&_mutex);//queue为空的时候无法消耗，解锁并等待  当线程被唤醒时，会自动重新获取这个互斥锁
        }
        //2.走到这里一定不为空
        *out = _q.front();
        _q.pop();
        //3.这时queue一定不满，可以唤醒p进行生产，这里可以有一定的策略
        // pthread_cond_signal(&_pcond);//只唤醒，被唤醒时会自动重新获取这个互斥锁
        pthread_mutex_unlock(&_mutex);//解锁
        pthread_cond_signal(&_pcond);//只唤醒，被唤醒时会自动重新获取这个互斥锁
        //这里也可以将唤醒的操作移到临界区的外面 
    }

    ~BlockQueue()
    {

    }
private:
    bool is_empty()
    {
        return _q.empty();
    }
    bool is_full()
    {
        return _q.size()==_maxcap;
    }
private:
    std::queue<T> _q;
    int _maxcap;//最大容量
    pthread_mutex_t _mutex;//管理该模型的锁
    pthread_cond_t _pcond;//生产者对应的条件变量
    pthread_cond_t _ccond;//消费者对应的条件变量 
};
